using System.Diagnostics;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.store
{


///
/// <summary> * Dynamic store that stores strings. </summary>
/// 
	internal class DynamicArrayStore : AbstractDynamicStore
	{
	// store version, each store ends with this string (byte encoded)
		private const string VERSION = "ArrayPropertyStore v0.9.5";

		private static enum ArrayType
		{
			ILLEGAL = 0,
			INT = 1,
			STRING = 2,
			BOOL = 3,
			DOUBLE = 4,
			FLOAT = 5,
			LONG = 6,
			BYTE = 7,
			CHAR = 8,
			SHORT = 10

//JAVA TO VB & C# CONVERTER TODO TASK: Enums cannot contain fields in .NET:
//			private int type;

//JAVA TO VB & C# CONVERTER TODO TASK: Enums cannot contain methods in .NET:
//			ArrayType(int type)
//		{
//			this.type = type;
//		}

//JAVA TO VB & C# CONVERTER TODO TASK: Enums cannot contain methods in .NET:
//			public byte byteValue()
//		{
//			return (byte) type;
//		}
		}

		public DynamicArrayStore<T1>(string fileName, Map<T1> config) : base(fileName, config)
		{
		}

		public DynamicArrayStore(string fileName) : base(fileName)
		{
		}

		public virtual string getTypeAndVersionDescriptor()
		{
			return VERSION;
		}

		public static void createStore(string fileName, int blockSize)
		{
			createEmptyStore(fileName, blockSize, VERSION);
		}

		private Collection<DynamicRecord> allocateFromInt(int startBlock, int[] array)
		{
			int size = array.Length * 4 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.INT.byteValue());
			foreach (int i in array)
			{
				buf.putInt(i);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromInt(int startBlock, int[] array)
		{
			int size = array.Length * 4 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.INT.byteValue());
			foreach (int i in array)
			{
				buf.putInt(i);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromShort(int startBlock, short[] array)
		{
			int size = array.Length * 2 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.SHORT.byteValue());
			foreach (short i in array)
			{
				buf.putShort(i);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromShort(int startBlock, short[] array)
		{
			int size = array.Length * 2 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.SHORT.byteValue());
			foreach (short i in array)
			{
				buf.putShort(i);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromString(int startBlock, string[] array)
		{
			int size = 5;
			foreach (string str in array)
			{
				size += 4 + str.Length * 2;
			}
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.STRING.byteValue());
			buf.putInt(array.Length);
			foreach (string str in array)
			{
				int length = str.Length;
				char[] chars = new char[length];
				str.getChars(0, length, chars, 0);
				buf.putInt(length * 2);
				foreach (char c in chars)
				{
					buf.putChar(c);
				}
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromBool(int startBlock, bool[] array)
		{
			int size = 5 + array.Length / 8;
			if (array.Length % 8 > 0)
			{
				size++;
			}
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.BOOL.byteValue());
			buf.putInt(array.Length);
			sbyte currentValue = 0;
			int byteItr = 0;
			foreach (bool b in array)
			{
				if (b)
				{
					currentValue += 1 << byteItr;
				}
				byteItr++;
				if (byteItr == 8)
				{
					buf.put(currentValue);
					byteItr = 0;
					currentValue = 0;
				}
			}
			if (byteItr != 0)
			{
				buf.put(currentValue);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromBool(int startBlock, bool[] array)
		{
			int size = 5 + array.Length / 8;
			if (array.Length % 8 > 0)
			{
				size++;
			}
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.BOOL.byteValue());
			buf.putInt(array.Length);
			sbyte currentValue = 0;
			int byteItr = 0;
			foreach (bool b in array)
			{
				if (b)
				{
					currentValue += 1 << byteItr;
				}
				byteItr++;
				if (byteItr == 8)
				{
					buf.put(currentValue);
					byteItr = 0;
					currentValue = 0;
				}
			}
			if (byteItr != 0)
			{
				buf.put(currentValue);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromDouble(int startBlock, double[] array)
		{
			int size = array.Length * 8 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.DOUBLE.byteValue());
			foreach (double d in array)
			{
				buf.putDouble(d);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromDouble(int startBlock, double[] array)
		{
			int size = array.Length * 8 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.DOUBLE.byteValue());
			foreach (double d in array)
			{
				buf.putDouble(d);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromFloat(int startBlock, float[] array)
		{
			int size = array.Length * 4 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.FLOAT.byteValue());
			foreach (float f in array)
			{
				buf.putFloat(f);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromFloat(int startBlock, float[] array)
		{
			int size = array.Length * 4 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.FLOAT.byteValue());
			foreach (float f in array)
			{
				buf.putFloat(f);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromLong(int startBlock, long[] array)
		{
			int size = array.Length * 8 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.LONG.byteValue());
			foreach (long l in array)
			{
				buf.putLong(l);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromLong(int startBlock, long[] array)
		{
			int size = array.Length * 8 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.LONG.byteValue());
			foreach (long l in array)
			{
				buf.putLong(l);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromByte(int startBlock, sbyte[] array)
		{
			int size = array.Length + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.BYTE.byteValue());
			buf.put(array);
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromByte(int startBlock, sbyte[] array)
		{
			int size = array.Length + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.BYTE.byteValue());
			foreach (sbyte b in array)
			{
				buf.put(b);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromChar(int startBlock, char[] array)
		{
			int size = array.Length * 2 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.CHAR.byteValue());
			foreach (char c in array)
			{
				buf.putChar(c);
			}
			return allocateRecords(startBlock, buf.array());
		}

		private Collection<DynamicRecord> allocateFromChar(int startBlock, char[] array)
		{
			int size = array.Length * 2 + 1;
			ByteBuffer buf = ByteBuffer.allocate(size);
			buf.put(ArrayType.CHAR.byteValue());
			foreach (char c in array)
			{
				buf.putChar(c);
			}
			return allocateRecords(startBlock, buf.array());
		}

		public override Collection<DynamicRecord> allocateRecords(int startBlock, object array)
		{
			if (array is int[])
			{
				return allocateFromInt(startBlock, (int[]) array);
			}
			if (array is int[])
			{
				return allocateFromInt(startBlock, (int[]) array);
			}
			if (array is string[])
			{
				return allocateFromString(startBlock, (string[]) array);
			}
			if (array is bool[])
			{
				return allocateFromBool(startBlock, (bool[]) array);
			}
			if (array is bool[])
			{
				return allocateFromBool(startBlock, (bool[]) array);
			}
			if (array is double[])
			{
				return allocateFromDouble(startBlock, (double[]) array);
			}
			if (array is double[])
			{
				return allocateFromDouble(startBlock, (double[]) array);
			}
			if (array is float[])
			{
				return allocateFromFloat(startBlock, (float[]) array);
			}
			if (array is float[])
			{
				return allocateFromFloat(startBlock, (float[]) array);
			}
			if (array is long[])
			{
				return allocateFromLong(startBlock, (long[]) array);
			}
			if (array is long[])
			{
				return allocateFromLong(startBlock, (long[]) array);
			}
			if (array is sbyte[])
			{
				return allocateFromByte(startBlock, (sbyte[]) array);
			}
			if (array is sbyte[])
			{
				return allocateFromByte(startBlock, (sbyte[]) array);
			}
			if (array is char[])
			{
				return allocateFromChar(startBlock, (char[]) array);
			}
			if (array is char[])
			{
				return allocateFromChar(startBlock, (char[]) array);
			}
			if (array is short[])
			{
				return allocateFromShort(startBlock, (short[]) array);
			}
			if (array is short[])
			{
				return allocateFromShort(startBlock, (short[]) array);
			}
			throw new IllegalArgumentException(array + " not a valid array type.");
		}

		public virtual object getRightArray(sbyte[] bArray)
		{
			ByteBuffer buf = ByteBuffer.wrap(bArray);
			sbyte type = buf.get();
			if (type == ArrayType.INT.byteValue())
			{
				int size = (bArray.Length - 1) / 4;
				Debug.Assert((bArray.Length - 1) % 4 == 0);
				int[] array = new int[size];
				for (int i = 0; i < size; i++)
				{
					array[i] = buf.getInt();
				}
				return array;
			}
			if (type == ArrayType.STRING.byteValue())
			{
				string[] array = new string[buf.getInt()];
				for (int i = 0; i < array.Length; i++)
				{
					int charLength = buf.getInt() / 2;
					char[] charBuffer = new char[charLength];
					for (int j = 0; j < charLength; j++)
					{
						charBuffer[j] = buf.getChar();
					}
					array[i] = new string(charBuffer);
				}
				return array;
			}
			if (type == ArrayType.BOOL.byteValue())
			{
				bool[] array = new bool[buf.getInt()];
				int byteItr = 1;
				sbyte currentValue = buf.get();
				for (int i = 0; i < array.Length; i++)
				{
					array[i] = (currentValue & byteItr) > 0 ? true : false;
					byteItr *= 2;
					if (byteItr == 256)
					{
						byteItr = 0;
						currentValue = buf.get();
					}
				}
				return array;
			}
			if (type == ArrayType.DOUBLE.byteValue())
			{
				int size = (bArray.Length - 1) / 8;
				Debug.Assert((bArray.Length - 1) % 8 == 0);
				double[] array = new double[size];
				for (int i = 0; i < size; i++)
				{
					array[i] = buf.getDouble();
				}
				return array;
			}
			if (type == ArrayType.FLOAT.byteValue())
			{
				int size = (bArray.Length - 1) / 4;
				Debug.Assert((bArray.Length - 1) % 4 == 0);
				float[] array = new float[size];
				for (int i = 0; i < size; i++)
				{
					array[i] = buf.getFloat();
				}
				return array;
			}
			if (type == ArrayType.LONG.byteValue())
			{
				int size = (bArray.Length - 1) / 8;
				Debug.Assert((bArray.Length - 1) % 8 == 0);
				long[] array = new long[size];
				for (int i = 0; i < size; i++)
				{
					array[i] = buf.getLong();
				}
				return array;
			}
			if (type == ArrayType.BYTE.byteValue())
			{
				int size = (bArray.Length - 1);
				sbyte[] array = new sbyte[size];
				buf.get(array);
				return array;
			}
			if (type == ArrayType.CHAR.byteValue())
			{
				int size = (bArray.Length - 1) / 2;
				Debug.Assert((bArray.Length - 1) % 2 == 0);
				char[] array = new char[size];
				for (int i = 0; i < size; i++)
				{
					array[i] = buf.getChar();
				}
				return array;
			}
			if (type == ArrayType.SHORT.byteValue())
			{
				int size = (bArray.Length - 1) / 2;
				Debug.Assert((bArray.Length - 1) % 2 == 0);
				short[] array = new short[size];
				for (short i = 0; i < size; i++)
				{
					array[i] = buf.getShort();
				}
				return array;
			}
			throw new InvalidRecordException("Unknown array type[" + type + "]");
		}

		public virtual object getArray(int blockId)
		{
			sbyte[] bArray = get(blockId);
			return getRightArray(bArray);
		}

		protected internal override bool versionFound(string version)
		{
			if (!version.StartsWith("ArrayPropertyStore"))
			{
			// non clean shutdown, need to do recover with right neo
				return false;
			}
			if (version.Equals("ArrayPropertyStore v0.9.3"))
			{
				rebuildIdGenerator();
				closeIdGenerator();
				return true;
			}
			throw new IllegalStoreVersionException("Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j.");
		}
	}
}